Skip to content

First-class Tailscale support (status, serve/funnel, doctor, docs) - #2451

Open
sethkarten wants to merge 6 commits into
mainfrom
rsi/tailscale-support
Open

sethkarten wants to merge 6 commits into
mainfrom
rsi/tailscale-support

Conversation

@sethkarten

@sethkarten sethkarten commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Motivation

Every AI harness needs to support Tailscale: agents run in containers, on laptops, and in CI; Tailscale is the standard zero-config way to reach them (user directive). This gives prime-agent first-class Tailscale awareness instead of leaving users to hand-wire it.

What it adds

  • prime-agent tailscale - tailnet state, MagicDNS name, and served endpoints (listen/path -> target from tailscale serve status --json)
  • prime-agent tailscale serve --port <n> [--funnel] - wraps tailscale serve --bg localhost:<n> / tailscale funnel --bg localhost:<n>; requires an explicit --port (never guesses a default), validates 1-65535 BEFORE any side effect, exits 1 on every failure
  • post-serve verification: tailscale can exit 0 after only printing an interactive enable URL without configuring anything (enableFeatureInteractive); the wrapper re-reads serve status --json and refuses success when the target is absent
  • --json machine-readable status
  • doctor integration (human mode only; doctor --json stays pure JSON)
  • docs: four patterns (Tailscale SSH remote control, serve/funnel exposure, MagicDNS reach from cloud containers, adding Tailscale's MCP server)

Backend-state diagnosis

BackendState drives the diagnosis, not just Self.Online: a node whose daemon is Running but currently unreachable is reported "up on a tailnet (currently offline)", not wrongly told to run tailscale up. Top-level MagicDNSSuffix is deprecated upstream; CurrentTailnet.MagicDNSSuffix is preferred when present.

Verification

  • 16 vitest tests: probe/stopped/offline states, serve argv construction (serve|funnel --bg localhost:<n> pinned exactly), post-serve verification (pending-enable detection), parser forms (--port n, --port=n, bare serve refusal, unknown subcommand errors), status failure diagnostics (empty-stderr case)
  • CLI detection uses Node ENOENT (no which dependency; Windows-safe)
  • serve spawn: stdin inherited (funnel's first enable prompts interactively), 60s timeout
  • dual-reviewer gate passed (Macroscope + Bugbot personas, empirical probes); all 13+ findings fixed in-branch
  • npm run check green (budget-clean: test lines <= source lines)

Note

Medium Risk
New CLI paths invoke Tailscale serve/funnel and can expose local services on the tailnet or publicly when --funnel is used; behavior is delegated to the external tailscale binary with validation rather than changes to core agent auth or data handling.

Overview
Adds first-class Tailscale to prime-agent via a new top-level tailscale command, wired through the command registry and public CLI dispatcher.

prime-agent tailscale (default status) probes the tailscale CLI, reports tailnet/MagicDNS/node state (including Running-but-offline vs stopped), lists active serve/funnel mappings from serve status --json, and supports --json. tailscale serve --port <n> [--funnel] wraps tailscale serve|funnel --bg localhost:<n> with required explicit ports (1–65535), pre-flight checks, and post-serve verification so exit 0 alone cannot claim success when the port is not actually served or funnel is not enabled.

prime-agent doctor (human output only, not --json) appends one-line Tailscale facts from the same probe. New docs/tailscale.md and a changelog entry document SSH remote control, serve/funnel, MagicDNS/containers, and Tailscale MCP. Vitest coverage uses a shimmed tailscale binary for probe, parser edge cases, and exact port matching.

Reviewed by Cursor Bugbot for commit 3c79aa9. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add tailscale CLI command for status, serve/funnel, and doctor

  • Registers the tailscale command group in command-registry.ts and dispatches it via public-command.ts
  • runTailscaleStatus probes the Tailscale CLI, reports tailnet and MagicDNS identity, lists locally served endpoints, and supports JSON output
  • runTailscaleServe validates ports, runs serve or funnel in background mode, and verifies the exact local target is configured before reporting success
  • runDoctor appends Tailscale installation and tailnet facts to human-readable reports; JSON and fix-mode output are unchanged
  • Adds tailscale.md documenting Tailscale SSH, port exposure, container networking, and remote MCP setup
  • Behavioral Change: runTailscaleServe exits non-zero if tailscaleStatusJson verification does not match the exact requested port or if funnel enablement is unconfirmed

Macroscope summarized 3c79aa9.

@github-actions

github-actions Bot commented Sep 18, 2026

Copy link
Copy Markdown

Prime Agent performance — completed

PR 3c79aa9c compared with main a8ae6269.

Overall: 1 regressed · 1 improved · 39 no clear change.

Metric Main This PR Change
Cold startup 752.4 ms 786.0 ms ≈ +33.5 ms (+4.46%)
Warm startup 546.0 ms 499.6 ms ≈ -46.4 ms (-8.50%)
Installation 5.84 s 6.04 s ≈ +0.20 s (+3.34%)
Compressed release artifacts 73.03 MB 72.99 MB ≈ -0.03 MB (-0.05%)
Installed footprint 594.40 MB 594.38 MB ≈ -0.03 MB (-0.00%)
Idle memory, summed RSS 641.59 MB 816.60 MB $\textcolor{#ba615e}{\textsf{↑ +175.01 MB (+27.28\%)}}$

Python runtime

Metric Main This PR Change
Python kernel startup 33.4 ms 33.1 ms ≈ -0.3 ms (-1.02%)
Python cell round trip 0.089 ms 0.093 ms ≈ +0.003 ms (+3.83%)
Empty bash command 2.2 ms 2.2 ms ≈ +0.013 ms (+0.56%)
Bash git status 2.8 ms 3.0 ms ≈ +0.2 ms (+8.75%)
Bash 32 KiB output 2.0 ms 2.2 ms ≈ +0.2 ms (+10.10%)
35 cells / 9 shell calls 27.6 ms 27.8 ms ≈ +0.2 ms (+0.88%)
Python interrupt to done 0.599 ms 0.527 ms ≈ -0.072 ms (-12.01%)
Python state snapshot 10.4 ms 9.8 ms ≈ -0.6 ms (-5.67%)
Python state restore 137.1 ms 127.7 ms ≈ -9.4 ms (-6.88%)
Python idle RSS 21.05 MB 21.11 MB ≈ +0.06 MB (+0.30%)
Python RSS after pandas workload 75.46 MB 75.47 MB ≈ +0.0082 MB (+0.01%)

Session transport

Metric Main This PR Change
Private frame decode, 32 MiB in 8 KiB chunks 18.1 ms 16.5 ms ≈ -1.6 ms (-8.95%)

UI interactions

Metric Main This PR Change
Resume large session (cold) 1,918.6 ms 2,002.1 ms ≈ +83.5 ms (+4.35%)
CPU, resume large session 2,000.0 ms 2,170.0 ms ≈ +170.0 ms (+8.50%)
Switch into large session 2,006.8 ms 1,728.0 ms ≈ -278.8 ms (-13.89%)
CPU, switch into large session 2,480.0 ms 2,030.0 ms ≈ -450.0 ms (-18.15%)
Open agents view from a session 156.5 ms 493.5 ms ≈ +337.0 ms (+215.31%)
CPU, open agents view 280.0 ms 790.0 ms ≈ +510.0 ms (+182.14%)
Full agents roster, many sessions 4.03 s 3.63 s ≈ -0.41 s (-10.05%)
CPU, full agents roster 1.25 s 0.83 s $\textcolor{#4e8763}{\textsf{↓ -0.42 s (-33.60\%)}}$
Open another session from agents view 2,048.1 ms 2,144.8 ms ≈ +96.8 ms (+4.73%)
CPU, open from agents view 1,150.0 ms 1,020.0 ms ≈ -130.0 ms (-11.30%)
Reopen resident large session 312.4 ms 298.2 ms ≈ -14.2 ms (-4.54%)
CPU, reopen resident session 320.0 ms 300.0 ms ≈ -20.0 ms (-6.25%)
Open subagent session at depth 6 17,821.6 ms 17,691.4 ms ≈ -130.2 ms (-0.73%)
CPU, open subagent at depth 6 4,850.0 ms 4,700.0 ms ≈ -150.0 ms (-3.09%)
Open chain parent from agents view 3,157.6 ms 3,161.0 ms ≈ +3.4 ms (+0.11%)
CPU, open chain parent 1,450.0 ms 1,450.0 ms ≈ +8.9e-13 ms (+0.00%)
Scheduled catalog, first request 431.6 ms 434.3 ms ≈ +2.7 ms (+0.62%)
CPU, scheduled catalog 830.0 ms 910.0 ms ≈ +80.0 ms (+9.64%)
Scheduled catalog, repeated request 0.5 ms 0.5 ms ≈ -0.025 ms (-4.71%)
CPU, repeated catalog 0.0 ms 0.0 ms ≈ +0.0 ms (N/A)
Cold worker with three catalog scans 580.5 ms 574.6 ms ≈ -5.9 ms (-1.02%)
CPU, cold worker and scans 390.0 ms 430.0 ms ≈ +40.0 ms (+10.26%)
UI memory after interactions 1,721.82 MB 1,918.91 MB ≈ +197.09 MB (+11.45%)

Sandbox cost: ~$0.1023 — no inference calls.
Run, logs, and downloadable raw results

Methodology and samples

Main resolved at 2026-09-21T12:46:04.266085+00:00. Harness a8ae6269.
Linux x64, 4 vCPU, 8 GB RAM, 20 GB disk; region us.
Image: node:24-bookworm@sha256:be23f54a88d34e8824c741b19b91064094f92c1c97b194144bfc8b50d67258e2.
Stock tools, skills, daemon, and Python bootstrap enabled; fresh homes and a fixed Git fixture.
Onboarding is dismissed; the editor starts without a selected model or submitted prompt.
Medians shown. Arrows require a 20% timing/memory change plus absolute floors and IQR.
These practical noise floors are not a statistical significance test.
Cold means stopped Prime processes; OS filesystem caches are not flushed.
No model requests or credentials. Installation excludes build/setup time.
Installer tarballs use loopback; npm/Python downloads use the network with fresh caches.
Artifact size counts release tarballs; footprint after first use includes registry packages.
MB is decimal. Summed RSS can double-count shared pages; PSS is recorded when available.
Provisioning, setup, and build durations are recorded separately in the raw results.
Kernel probes use the installed JSONL runtime, outside the TUI/TypeScript host.
Per trial: 50 Python cells, 5 calls per shell case, and one 35-cell mix (9 git status calls).
Cell/shell values are batch means; other runtime timings are single operations.
State fixture: a 10,000-row × 8-column integer DataFrame and a 10,000-integer list.
Restore runs in a fresh kernel, including pandas imports; kernel startup is excluded.
Kernel RSS covers the isolated Python process; loaded RSS follows the pandas workload.
Transport benches run node against the prepared source build, outside the installed home.
Frame decode times one 32 MiB private frame, snapshot-chunk header, pushed in
8 KiB chunks; the wire shape of multi-MB frames on the daemon-worker channels.
UI trials use a fresh fixture set: 194 top-level sessions including one ~40 MB transcript,
40 ledger fan-out children, and a 6-deep subagent chain (~46 spawn edges).
Large fixtures hold 1,999 complete triples (~5 MB JSONL); medium 119; subagents 399 each.
Interactions: cold --resume of a large session, warm /resume switch, left-arrow to agents view,
roster settle with many saved sessions, search-and-open of another large session,
reattaching to that resident session, opening the chain parent, and drilling to depth 6.
Readiness is the rendered transcript tail plus a confirmed editor echo.
CPU metrics sum utime+stime across the whole benchmark-user process tree per interaction.
UI memory sums RSS after the interactions; PTY byte counts are in the raw results.
A separate catalog fixture has 2,300 sessions, 2,298 edges, and 13 paused scheduled-job owners.
Catalog timings cover first/repeated reads and cold worker creation under three pending scans.
All expected jobs and owner metadata are checked; worker readiness excludes TUI rendering.
Costs estimate full sandbox lifetimes at configured rates, including setup and build.
Budget target: $1; not a billing cap. Performance changes are informational.
Failed or incomplete execution fails the workflow; saved artifacts remain available.
Each side stops a phase after 2 identical consecutive failures.
Skipped trials are not attempted samples. Warm startup requires a successful cold launch.

Metric Main successful/attempted PR successful/attempted Main spread PR spread
Cold startup 10/10 10/10 IQR 66.2 ms IQR 128.3 ms
Warm startup 10/10 10/10 IQR 84.2 ms IQR 37.0 ms
Installation 3/3 3/3 range 0.35 s range 0.25 s
Compressed release artifacts 1/1 1/1
Installed footprint 1/1 1/1
Idle memory, summed RSS 10/10 10/10 IQR 2.83 MB IQR 4.16 MB
Python kernel startup 10/10 10/10 IQR 3.7 ms IQR 2.3 ms
Python cell round trip 10/10 10/10 IQR 0.032 ms IQR 0.040 ms
Empty bash command 10/10 10/10 IQR 0.4 ms IQR 0.3 ms
Bash git status 10/10 10/10 IQR 0.6 ms IQR 0.3 ms
Bash 32 KiB output 10/10 10/10 IQR 0.5 ms IQR 0.2 ms
35 cells / 9 shell calls 10/10 10/10 IQR 4.5 ms IQR 1.9 ms
Python interrupt to done 10/10 10/10 IQR 0.067 ms IQR 0.081 ms
Python state snapshot 10/10 10/10 IQR 0.7 ms IQR 0.5 ms
Python state restore 10/10 10/10 IQR 22.1 ms IQR 8.7 ms
Python idle RSS 10/10 10/10 IQR 0.20 MB IQR 0.18 MB
Python RSS after pandas workload 10/10 10/10 IQR 0.08 MB IQR 0.40 MB
Private frame decode, 32 MiB in 8 KiB chunks 10/10 10/10 IQR 4.9 ms IQR 3.9 ms
Resume large session (cold) 3/3 3/3 range 610.3 ms range 833.6 ms
CPU, resume large session 3/3 3/3 range 320.0 ms range 400.0 ms
Switch into large session 3/3 3/3 range 530.3 ms range 109.0 ms
CPU, switch into large session 3/3 3/3 range 550.0 ms range 70.0 ms
Open agents view from a session 3/3 3/3 range 417.6 ms range 368.4 ms
CPU, open agents view 3/3 3/3 range 700.0 ms range 660.0 ms
Full agents roster, many sessions 3/3 3/3 range 0.0027 s range 0.00059 s
CPU, full agents roster 3/3 3/3 range 0.27 s range 0.22 s
Open another session from agents view 3/3 3/3 range 82.0 ms range 110.2 ms
CPU, open from agents view 3/3 3/3 range 180.0 ms range 90.0 ms
Reopen resident large session 3/3 3/3 range 54.3 ms range 25.6 ms
CPU, reopen resident session 3/3 3/3 range 60.0 ms range 50.0 ms
Open subagent session at depth 6 3/3 3/3 range 481.6 ms range 269.9 ms
CPU, open subagent at depth 6 3/3 3/3 range 70.0 ms range 230.0 ms
Open chain parent from agents view 3/3 3/3 range 147.0 ms range 216.6 ms
CPU, open chain parent 3/3 3/3 range 130.0 ms range 110.0 ms
Scheduled catalog, first request 3/3 3/3 range 22.0 ms range 164.0 ms
CPU, scheduled catalog 3/3 3/3 range 30.0 ms range 170.0 ms
Scheduled catalog, repeated request 3/3 3/3 range 0.043 ms range 0.3 ms
CPU, repeated catalog 3/3 3/3 range 0.0 ms range 0.0 ms
Cold worker with three catalog scans 3/3 3/3 range 63.2 ms range 201.6 ms
CPU, cold worker and scans 3/3 3/3 range 90.0 ms range 120.0 ms
UI memory after interactions 3/3 3/3 range 63.14 MB range 279.01 MB

@sethkarten
sethkarten enabled auto-merge (squash) September 18, 2026 06:12
Comment thread packages/coding-agent/src/cli/tailscale.ts Fixed
Comment thread packages/coding-agent/src/cli/tailscale.ts Outdated
Comment thread packages/coding-agent/src/cli/tailscale.ts
Comment thread packages/coding-agent/src/cli/tailscale.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/coding-agent/src/cli/tailscale.ts Outdated
Comment thread packages/coding-agent/src/cli/tailscale.ts Outdated
Comment thread packages/coding-agent/src/cli/tailscale.ts Outdated
@sethkarten sethkarten changed the title [RSI] First-class Tailscale support (status, serve/funnel, doctor, docs) First-class Tailscale support (status, serve/funnel, doctor, docs) Sep 18, 2026
@sethkarten
sethkarten force-pushed the rsi/tailscale-support branch 2 times, most recently from 9d87c71 to 99b8beb Compare September 18, 2026 06:43
Comment thread packages/coding-agent/src/cli/public-command.ts Outdated
Comment thread packages/coding-agent/src/cli/tailscale.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/coding-agent/src/cli/tailscale.ts
@sethkarten
sethkarten force-pushed the rsi/tailscale-support branch from 99b8beb to 75cac35 Compare September 18, 2026 07:42
Comment thread packages/coding-agent/src/cli/tailscale.ts
Comment thread packages/coding-agent/docs/tailscale.md Outdated
Comment thread packages/coding-agent/docs/tailscale.md
Comment thread packages/coding-agent/src/cli/tailscale.ts
Comment thread packages/coding-agent/src/cli/tailscale.ts Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/coding-agent/src/cli/tailscale.ts
Comment thread packages/coding-agent/src/cli/tailscale.ts
Comment thread packages/coding-agent/src/cli/tailscale.ts Outdated
@sethkarten
sethkarten force-pushed the rsi/tailscale-support branch from 75cac35 to 147f6ad Compare September 18, 2026 08:41
Comment thread packages/coding-agent/src/cli/tailscale.ts Outdated
Comment thread packages/coding-agent/.changes/tailscale-support.md Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/coding-agent/src/cli/tailscale.ts
Comment thread packages/coding-agent/src/cli/tailscale.ts Outdated
@sethkarten
sethkarten force-pushed the rsi/tailscale-support branch from 147f6ad to 5170bc3 Compare September 18, 2026 09:12
Comment thread packages/coding-agent/src/cli/tailscale.ts
Comment thread packages/coding-agent/src/cli/tailscale.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/coding-agent/src/cli/tailscale.ts
Comment thread packages/coding-agent/src/cli/tailscale.ts
…funnel, doctor, docs)

prime-agent tailscale: tailnet and MagicDNS detection with teaching
errors, serve --port [--funnel] wrapping tailscale serve/funnel for any
local bridge, a Tailscale section in doctor, and docs for the three
patterns (Tailscale SSH remote control, tailnet exposure, MagicDNS
reach from cloud containers + the official Tailscale MCP connector).
No new dependencies: shells out to the tailscale CLI detected at runtime.
@sethkarten
sethkarten force-pushed the rsi/tailscale-support branch from 5170bc3 to 295255b Compare September 18, 2026 09:44
@sethkarten

Copy link
Copy Markdown
Contributor Author

CORRECTION (process incident, transparency): replies at 147f6ad and 5170bc3 claimed some fixes that were lost from the tree before those commits were pushed. Audit of the pushed commits shows these claims were NOT in those commits but ARE now verifiably in 295255b (grep-verified in the pushed head):

  • ENOENT vs other-spawn-error taxonomy in probeTailscale (claimed at 147f6ad)
  • error-before-missing-CLI ordering in status and serve (claimed at 147f6ad)
  • single-probe --json (payload and exit code from the same probe; claimed at 147f6ad)
  • spawnSyncHidden at all spawn sites (claimed at 147f6ad)
  • killSignal: "SIGKILL" (claimed at 5170bc3; separate correction posted on that thread)
  • tailscaleStatusJson unexport (claimed at 5170bc3)

Root cause: a multi-step edit round where one edit step mutated the file in memory but was not persisted before an inspection step, and a later step re-read the file and wrote a tree missing the unpersisted change. Fixes that WERE correctly present at those commits: funnel AllowFunnel check, docs (userspace networking, Tailscale SSH prerequisites), changeset wording, host-suffix comment. All four of today's new findings trace exactly to the lost edits and are fixed at 295255b with tests green and the full check chain green. My process now greps the COMMIT content before posting any "fixed at " reply.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/coding-agent/src/cli/tailscale.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 70fb80c. Configure here.

Comment thread packages/coding-agent/src/cli/tailscale.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants